namespace ScrumSprintMonitor.TestbenchApp
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel.Composition;
	using System.ComponentModel.Composition.Hosting;
	using System.Diagnostics.CodeAnalysis;
	using System.IO;
	using System.Linq;
	using System.Reflection;
	using System.Security.Permissions;
	using System.Windows;
	using System.Windows.Forms;

	using Extensibility;
	using Extensibility.Events;

	using Implementation;

	using Infrastructure;
	using Infrastructure.Configuration;

	using Microsoft.Practices.Composite.Events;
	using Microsoft.Practices.Composite.Logging;
	using Microsoft.Practices.Composite.Modularity;
	using Microsoft.Practices.Composite.Presentation.Regions;
	using Microsoft.Practices.Composite.Regions;
	using Microsoft.Practices.ServiceLocation;

	using Ninject.Core;

	using NinjectContrib.CompositePresentation;

	using UI.Wpf.Avatars;

	using Views.TestbenchView;

	#endregion

	public class Bootstrapper : NinjectBootstrapper,
	                            IDisposable
	{
		private readonly Assembly assembly = Assembly.GetExecutingAssembly();
		private CompositionContainer container;
		private IEventAggregator eventAggregator;
		private ILogger<App> logger;
		private ILoggerFacade loggerFacade;
		private NinjectContainerAbstraction ninjectContainerAbstraction;
		private Window primaryMonitorShell;

		[SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
		public Bootstrapper()
		{
			DesignerProperties.GetIsInDesignMode = (object x) => StubDesignerProperties.GetIsInDesignMode(null);

			AddIns = new List<ScrumSprintMonitorAddIn>();
			AdditionalKernelModules = new List<Ninject.Core.IModule>();

			System.Windows.Application.Current.Exit += OnApplicationExit;
		}

		[ImportMany(typeof(ScrumSprintMonitorAddIn))]
		[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
			Justification = "The composition engine must be able to set this property")]
		public IList<ScrumSprintMonitorAddIn> AddIns { get; set; }

		[ImportMany(typeof(Ninject.Core.IModule))]
		[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
			Justification = "The composition engine must be able to set this property")]
		public IList<Ninject.Core.IModule> AdditionalKernelModules { get; set; }

		protected override ILoggerFacade LoggerFacade
		{
			get
			{
				if (this.loggerFacade == null)
				{
					this.loggerFacade = new Log4NetLoggerFacade();
				}

				return this.loggerFacade;
			}
		}

		#region IDisposable Members

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion

		~Bootstrapper()
		{
			Dispose(false);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this.container != null)
				{
					this.container.Dispose();
					this.container = null;
				}
			}
		}

		private bool Compose()
		{
			this.logger.Debug("Composing MEF container...");

			var aggregateCatalog = new AggregateCatalog();
			string addInsBinFolder = Path.Combine(Path.GetDirectoryName(this.assembly.Location), @"AddIns");

			aggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(WpfUriAvatar).Assembly));
			AddCatalogFromFolderPath(addInsBinFolder, aggregateCatalog);

			this.logger.Debug("Found {0} parts in {1} MEF catalogs:", aggregateCatalog.Parts.Count(), aggregateCatalog.Catalogs.Count);
			foreach (var part in aggregateCatalog.Parts)
			{
				this.logger.Debug("{0}", part.ToString());
			}

			this.container = new CompositionContainer(aggregateCatalog);

			var batch = new CompositionBatch();
			batch.AddPart(this);
			batch.AddPart(Kernel.Get<IProcessTemplateAdapterProvider>());
			batch.AddPart(Kernel.Get<IAvatarProviderStore>());
			batch.AddExportedValue(ServiceLocator.Current.GetInstance<IServiceProvider>());
			batch.AddExportedValue<ICompositionService>(this.container);

			try
			{
				this.container.Compose(batch);
			}
			catch (CompositionException compositionException)
			{
				this.logger.Fatal(compositionException, "Found exception while composing MEF container.");

				System.Windows.MessageBox.Show(compositionException.ToString());
				return false;
			}

			return true;
		}

		private void AddCatalogFromFolderPath(string binFolder, AggregateCatalog aggregateCatalog)
		{
			if (Directory.Exists(binFolder))
			{
				this.logger.Debug("Creating MEF DirectoryCatalog from {0}...", binFolder);

				var addinsDirectoryCatalog = new DirectoryCatalog(binFolder);
				aggregateCatalog.Catalogs.Add(addinsDirectoryCatalog);
			}
			else
			{
				this.logger.Debug("{0} addin folder not found.", binFolder);
			}
		}

		/// <summary>
		/// Shows screen saver by creating one instance of MainWindow for each monitor.
		/// 
		/// Note: uses WinForms's Screen class to get monitor info.
		/// </summary>
		private Window ShowScreensaver()
		{
			NinjectContainerAbstraction.EnsureConfigurationDependentModulesAreSetUp();

			this.logger.Info("Starting screensaver...");

			bool multiMonitor = ServiceLocator.Current.GetInstance<IShellLayoutConfiguration>().MultiMonitor;

			//creates window on primary screen
			var primaryShell = Kernel.Get<PrimaryMonitorShell>();
			primaryShell.WindowStartupLocation = WindowStartupLocation.Manual;
			primaryShell.WindowState = WindowState.Maximized;
			primaryShell.WindowStyle = WindowStyle.ToolWindow;

			this.primaryMonitorShell = primaryShell;
			MakeWindowFillThisMonitor(Screen.AllScreens.First(), primaryShell);

			ShowMonitorWindow(primaryShell);

			if (multiMonitor)
			{
				var secondaryMonitor = Screen.AllScreens[1 % Screen.AllScreens.Length];

				var secondaryShell = Kernel.Get<SecondaryMonitorShell>();
				secondaryShell.WindowStartupLocation = WindowStartupLocation.Manual;
				secondaryShell.WindowStyle = WindowStyle.ToolWindow;
				MakeWindowFillThisMonitor(secondaryMonitor, secondaryShell);

				//show non-primary screen windows
				ShowMonitorWindow(secondaryShell);

				RegionManager.SetRegionManager(secondaryShell, Kernel.Get<IRegionManager>());
				RegionManager.UpdateRegions();
			}

			System.Windows.Application.Current.MainWindow = primaryShell;
			System.Windows.Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;

			this.logger.Info(multiMonitor ? "Started screensaver in multi-monitor mode." : "Started screensaver in single monitor mode.", Category.Info,
			                 Priority.Low);

			return primaryShell;
		}

		private static void MakeWindowFillThisMonitor(Screen monitor, Window shell)
		{
			var location = monitor.Bounds;
			if (shell.WindowStyle == WindowStyle.ToolWindow)
			{
				//covers entire monitor
				shell.Left = location.X - SystemParameters.ResizeFrameVerticalBorderWidth;
				shell.Top = location.Y - SystemParameters.ResizeFrameHorizontalBorderHeight;
				shell.Width = location.Width + SystemParameters.ResizeFrameVerticalBorderWidth * 2;
				shell.Height = location.Height + SystemParameters.ResizeFrameHorizontalBorderHeight * 2;
			}
			else
			{
				//covers entire monitor
				shell.Left = location.X;
				shell.Top = location.Y;
				shell.Width = location.Width;
				shell.Height = location.Height;
			}
		}

		private void ShowMonitorWindow(Window window)
		{
			window.ShowActivated = true;
			window.Show();

			var monitorStartedEvent = this.eventAggregator.GetEvent<MonitorWindowShownEvent>();

			monitorStartedEvent.Publish(window);
		}

		private void LoadAddIn(ScrumSprintMonitorAddIn addIn)
		{
			this.logger.Info("Loading add-in {0}...", addIn.GetType().Name);

			addIn.Load();

			this.logger.Info("Loaded add-in {0}.", addIn.GetType().Name);
		}

		private void UnloadAddIn(ScrumSprintMonitorAddIn addIn)
		{
			this.logger.Info("Unloading add-in {0}...", addIn.GetType().Name);

			var disposable = addIn as IDisposable;
			addIn.Unload();
			if (disposable != null)
			{
				disposable.Dispose();
			}

			this.logger.Info("Unloaded add-in {0}.", addIn.GetType().Name);
		}

		private void UnloadAddIns()
		{
			if (AddIns != null)
			{
				foreach (var addIn in AddIns)
				{
					UnloadAddIn(addIn);
				}
				AddIns.Clear();
			}
		}

		private void OnApplicationExit(object sender, ExitEventArgs e)
		{
			if (this.primaryMonitorShell != null)
			{
				this.logger.Info("Shutting down screensaver...");
			}

			UnloadAddIns();

			if (this.container != null)
			{
				this.container.Dispose();
			}

			this.ninjectContainerAbstraction.Dispose();
		}

		#region Overrides of NinjectBootstrapper

		protected override IKernel CreateKernel()
		{
			this.ninjectContainerAbstraction = NinjectContainerAbstraction.EnsureContainerIsSetUp();

			return this.ninjectContainerAbstraction.Instance;
		}

		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
		protected override void ConfigureKernel()
		{
			base.ConfigureKernel();

			Kernel.Load(new InlineModule(m => m.Bind<PrimaryMonitorShell>().ToSelf(), m => m.Bind<SecondaryMonitorShell>().ToSelf()));

			NinjectContainerAbstraction.RegisterModule<NinjectAppRuntimeModule>();
			NinjectContainerAbstraction.RegisterModule<NinjectAppConfigurationDependentModule>(true);
			this.ninjectContainerAbstraction.LoadRegisteredModules(false);

			this.eventAggregator = Kernel.Get<IEventAggregator>();
			this.logger = Kernel.Get<ILogger<App>>();

			if (Compose())
			{
				Kernel.Load(AdditionalKernelModules);

				foreach (var addIn in AddIns)
				{
					LoadAddIn(addIn);
				}
			}
			else
			{
				System.Windows.Application.Current.Shutdown();
			}
		}

		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
		protected override IModuleCatalog GetModuleCatalog()
		{
			return new DirectoryModuleCatalog
			       	{
			       		ModulePath = Path.GetDirectoryName(this.assembly.Location)
			       	};
		}

		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
		protected override DependencyObject CreateShell()
		{
			ShowScreensaver();

			var testbenchWindow = new TestbenchWindow
			                      	{
			                      		ParentWindow = this.primaryMonitorShell
			                      	};
			testbenchWindow.Show();

			System.Windows.Application.Current.MainWindow = testbenchWindow;
			System.Windows.Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;

			return this.primaryMonitorShell;
		}

		protected override void InitializeModules()
		{
			if (this.primaryMonitorShell == null)
			{
				return;
			}

			base.InitializeModules();

			var monitorStartedEvent = this.eventAggregator.GetEvent<MonitorStartedEvent>();

			monitorStartedEvent.Publish(EventArgs.Empty);
		}

		#endregion
	}
}